#include "common.h"


typedef struct Signal {
    int signo;
    void  (*handler)(int signo);
} Signal;

pid_t g_Pid;
int g_Process;

sig_atomic_t  signal_reap;
sig_atomic_t  signal_sigio;
sig_atomic_t  signal_sigalrm;
sig_atomic_t  signal_terminate;
sig_atomic_t  signal_quit;
sig_atomic_t  signal_debug_quit;

void signal_handler(int signo);

Signal  g_signals[] = {

    { SIGTERM, signal_handler },

    { SIGQUIT, signal_handler },

    { SIGALRM, signal_handler },

    { SIGINT, signal_handler },

    { SIGIO, signal_handler },

    { SIGCHLD, signal_handler },

    { SIGSYS, SIG_IGN },

    { SIGPIPE, SIG_IGN },

    { 0, NULL }
};

int Process_Daemon()
{
    int  fd;

    switch (fork()) {
    case -1:
        return PROCESS_ERR;
    case 0:
        break;
    default:
        exit(0);
    }

    g_Pid = getpid();

    if (setsid() == -1) {
        return PROCESS_ERR;
    }

    fd = open("/dev/null", O_RDWR);
    if (fd == -1) {
        return PROCESS_ERR;
    }

    if (dup2(fd, STDIN_FILENO) == -1) {
        return PROCESS_ERR;
    }

    if (dup2(fd, STDOUT_FILENO) == -1) {
        return PROCESS_ERR;
    }

    /* Notice: We will not dup2 the STDERR because we need print some infomation on console */
    
    if (fd > STDERR_FILENO) {
        if (close(fd) == -1) {
            return PROCESS_ERR;
        }
    }

    return PROCESS_OK;
}

void Process_ExitMaster()
{
    
}

int Process_StartMaster()
{
    sigset_t    set;
    
    sigemptyset(&set);
    sigaddset(&set, SIGCHLD);
    sigaddset(&set, SIGALRM);
    sigaddset(&set, SIGIO);
    sigaddset(&set, SIGINT);
    sigaddset(&set, SIGTERM);
    sigaddset(&set, SIGQUIT);

    if (sigprocmask(SIG_BLOCK, &set, NULL) == -1) {
        return PROCESS_ERR;
    }

    sigemptyset(&set);

    /* Indicate the number of children, the cores of cpu is preferrd */
    if (Process_StartWorker(8) != PROCESS_OK) {
        return PROCESS_ERR;
    }

    for ( ;; ) {
        /* Block here until extern signal comes */
        sigsuspend(&set);

        if (signal_reap) {
            signal_reap = 0;
            /* if child exit, we fork another one */
            Log_Write(g_server->log, "Child exit");
            Process_StartWorker(1);
        }

        if (signal_terminate) {
            Log_Write(g_server->log, "Signal Terminate");
            continue;
        }

        if (signal_quit) {
            Log_Write(g_server->log, "Signal Quit");
            continue;
        }

    }
}

void Process_WorkerLoop()
{
    int rc;
    sigset_t set;

    g_Pid = getpid();
    g_Process = PROCESS_WORKER;    

    sigemptyset(&set);
    if (sigprocmask(SIG_SETMASK, &set, NULL) == -1) {
	    Log_Write(g_server->log, "Worker Set Signal Mask Failed");    
    }
 
    rc = EVENT_Init(g_server);
    if (rc != EVENT_OK) {
        Log_Write(g_server->log, "Worker Init Event Failed");
        exit(6);
    }
    Log_Write(g_server->log, "Worker Start..");

    /* The logic loop */
    while(1) {
        /* we can make our program process both file IO and timer event */
        rc = EVENT_Process_Events(EVENT_FILE_EVENT | EVENT_TIME_EVENTS);
        if (rc != EVENT_OK) {
           Log_Write(g_server->log, "Worker Quit");
           exit(6);
        }
    }

    exit(6);
}

int Process_StartWorker(int num)
{
    int i;
    pid_t pid;
    
    for (i = 0; i < num; i++) {
        pid = fork();
        switch (pid) {

        case -1:
            return PROCESS_ERR;

        case 0:
            Process_WorkerLoop();
            break;

        default:
            break;
        }
    }
    return PROCESS_OK;
    
}


static void Process_Get_Status()
{
    int status;
    pid_t   pid;
    int     err;

    /* Parent Process will waitpid(...) to avoid the Zombie process or get the exitd child status when child exits */
    for ( ;; ) {
        pid = waitpid(-1, &status, WNOHANG);

        if (pid == 0) {
            return;
        }

        if (pid == -1) {
            err = errno;

            if (err == EINTR) {
                continue;
            }

            if (err == ECHILD) {
                return;
            }
            return;
        }

        if (WIFEXITED(status)) {
            Log_Write(g_server->log, "Child %d Exit Normally %d", pid, WEXITSTATUS(status));
        } else if (WIFSIGNALED(status)){
            Log_Write(g_server->log, "Child %d Exit by signal %d", pid, WTERMSIG(status));
        } else {
            Log_Write(g_server->log, "Child %d Exit exception", pid);
        }

    }
}

void signal_handler(int signo)
{	
    char    *action;
    int err;
    Signal *sig;

    err = errno;

    for (sig = g_signals; sig->signo != 0; sig++) {
        if (sig->signo == signo) {
            break;
        }
    }

    switch (g_Process) {

    case PROCESS_MASTER:
        switch (signo) {

        case SIGQUIT:
            signal_quit = 1;
            break;

        case SIGTERM:
        case SIGINT:
            signal_terminate = 1;
            break;

        case SIGALRM:
            signal_sigalrm = 1;
            break;

        case SIGIO:
            signal_sigio = 1;
            break;

        case SIGCHLD:
            signal_reap = 1;
            break;
        }

        break;

    case PROCESS_WORKER:
        switch (signo) {
            
        case SIGQUIT:
            signal_quit = 1;
            break;

        case SIGTERM:
        case SIGINT:
            signal_terminate = 1;
            break;
        case SIGIO:
            break;
        }

        break;
    }

    if (signo == SIGCHLD) {
        Process_Get_Status();
    }

    errno = err;
}

int Init_Signal()
{
    Signal *sig;
    struct sigaction   sa;

    for (sig = g_signals; sig->signo != 0; sig++) {
        memset(&sa, 0, sizeof(struct sigaction));
        sa.sa_handler = sig->handler;
        sigemptyset(&sa.sa_mask);
        if (sigaction(sig->signo, &sa, NULL) == -1) {
            Log_Write(g_server->log, "Set sigaction failed");
            return PROCESS_ERR;
        }
    }

    return PROCESS_OK;
}
